package com.rybbaby.tss.core.hqlsearch;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.Transient;

import com.rybbaby.tss.core.annotation.QueryTimeFormat;
import com.rybbaby.tss.core.hqlsearch.parse.impl.DoubleParseImpl;
import com.rybbaby.tss.core.hqlsearch.parse.impl.ShortParseImpl;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Restrictions;
import com.rybbaby.tss.core.consts.CommonConsts;
import com.rybbaby.tss.core.hqlsearch.parse.IHqlParse;
import com.rybbaby.tss.core.hqlsearch.parse.impl.BigDecimalParseImpl;
import com.rybbaby.tss.core.hqlsearch.parse.impl.BooleanParseImpl;
import com.rybbaby.tss.core.hqlsearch.parse.impl.FloatParseImpl;
import com.rybbaby.tss.core.hqlsearch.parse.impl.IntegerParseImpl;
import com.rybbaby.tss.core.hqlsearch.parse.impl.LongParseImpl;
import com.rybbaby.tss.core.hqlsearch.parse.impl.StringParseImpl;

/**
 * @description DetachedCriteria语句条件生产工具
 * 
 * @author 谢进伟
 * 
 * @createDate 2015-11-11 上午10:03:02
 */
public class HqlGenerateUtil {
	
	/** 日志对象 **/
	private static Logger log = Logger.getLogger(HqlGenerateUtil.class);
	/** 范围查询起始值分隔标识 */
	private static final String BEGIN = "_begin";
	/** 范围查询结束值分隔标识 */
	private static final String END = "_end";
	/** 属性解析器集合 */
	private static Map<String , IHqlParse> map = new HashMap<String , IHqlParse>();
	/** 标准时间格式化器 */
	private static final SimpleDateFormat time = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
	
	static {
		map.put("class java.lang.Integer" , new IntegerParseImpl());
		map.put("class java.lang.Short" , new ShortParseImpl());
		map.put("class java.lang.String" , new StringParseImpl());
		map.put("class java.lang.Long" , new LongParseImpl());
		map.put("class java.lang.Double" , new DoubleParseImpl());
		map.put("class java.lang.Float" , new FloatParseImpl());
		map.put("class java.lang.Boolean" , new BooleanParseImpl());
		map.put("class java.math.BigDecimal" , new BigDecimalParseImpl());
		log.info("现有DetachedCriteria解析器：" + Arrays.deepToString(map.keySet().toArray()));
	}
	
	/**
	 * 自动生成查询条件HQL 模糊查询 不带有日期组合
	 * 
	 * @param dc
	 *            条件组装器
	 * @param searchObj
	 *            视图或表对应的实体
	 */
	public static void fillDetachedCriteria(DetachedCriteria dc , Object searchObj) {
		installHqlCriteriaJoinAlias(dc , searchObj , null , "");
	}
	
	/**
	 * 自动条件条件组装器中填充条件
	 * 
	 * @param dc
	 *            条件组装器
	 * @param searchObj
	 *            视图或表对应的实体
	 * @param param_lock
	 *            条件参数，param_lock的key必须是searchObj中存在的属性<br>
	 *            <span
	 *            style="color:red;">若有属性需要进行范围值检索(一般属性为数值类型或时间类型时可使用范围检索)，
	 *            则设置需要进行范围检索的起始值key为：
	 *            属性名_begin,结束值key为：属性名_end<span>
	 */
	public static void fillDetachedCriteria(DetachedCriteria dc , Object searchObj , Map<String , String []> parameterMap) {
		Map<String , String []> param_noLock = new HashMap<String , String []>();
		param_noLock.putAll(parameterMap);
		installHqlCriteriaJoinAlias(dc , searchObj , param_noLock , "");
	}
	
	/**
	 * 填充条件并添加Alias别名的查询
	 * 
	 * @param dc
	 *            条件组装器
	 * @param searchObj
	 *            视图或表对应的实体
	 * @param parameterMap
	 *            条件参数，param_lock的key必须是searchObj中存在的属性<br>
	 *            <span
	 *            style="color:red;">若有属性需要进行范围值检索(一般属性为数值类型或时间类型时可使用范围检索)，
	 *            则设置需要进行范围检索的起始值key为：
	 *            属性名_begin,结束值key为：属性名_end<span>
	 * @param alias
	 *            视图或表对应的实体的别名
	 */
	private static void installHqlCriteriaJoinAlias(DetachedCriteria dc , Object searchObj , Map<String , String []> parameterMap , String alias) {
		PropertyDescriptor origDescriptors [] = PropertyUtils.getPropertyDescriptors(searchObj.getClass());
		String aliasName , name , type;
		Class<?> cls = searchObj.getClass();
		Field fields [] = cls.getDeclaredFields();
		List<String> fieldList = new ArrayList<String>();
		for(Field f : fields) {
			fieldList.add(f.getName());
		}
		for(int i = 0 ; i < origDescriptors.length ; i++) {
			aliasName = (alias.equals("") ? "" : alias + ".") + origDescriptors[i].getName();
			name = origDescriptors[i].getDisplayName();
			type = origDescriptors[i].getPropertyType().toString();
			try {
				if(judgedIsUselessField(name) || !PropertyUtils.isReadable(searchObj , name)) {
					continue;
				}
				Field field = cls.getDeclaredField(name);
				// 添加 判断是否有区间值
				String beginValue = null;
				String endValue = null;
				if(parameterMap != null && parameterMap.containsKey(aliasName + BEGIN)) {
					beginValue = parameterMap.get(aliasName + BEGIN)[0].trim();
				}
				if(parameterMap != null && parameterMap.containsKey(aliasName + END)) {
					endValue = parameterMap.get(aliasName + END)[0].trim();
				}
				Object value = PropertyUtils.getSimpleProperty(searchObj , name);
				// 根据类型分类处理
				if(type.contains("class java.lang") || type.contains("class java.math")) {
					Transient transientField = origDescriptors[i].getReadMethod().getAnnotation(Transient.class);
					if(transientField == null) {
						transientField = field.getAnnotation(Transient.class);
					}
					if(transientField != null) {// 标识为瞬时的属性不参与数据库操作
						continue;
					}
					if(parameterMap == null && fieldList.contains(name)) {
						map.get(type).addCriteria(dc , aliasName , value);
					} else if(fieldList.contains(name)) {
						map.get(type).addCriteria(dc , aliasName , value , beginValue , endValue);
					}
				} else if("class java.util.Date".equals(type) || "class java.sql.Timestamp".equals(type)) {
					Transient transientField = origDescriptors[i].getReadMethod().getAnnotation(Transient.class);
					if(transientField == null) {
						transientField = field.getAnnotation(Transient.class);
					}
					if(transientField != null) {// 标识为瞬时的属性不参与数据库操作
						continue;
					}
					QueryTimeFormat format = origDescriptors[i].getReadMethod().getAnnotation(QueryTimeFormat.class);
					if(format == null) {
						format = field.getAnnotation(QueryTimeFormat.class);
					}
					SimpleDateFormat userDefined = null;
					if(format != null) {
						userDefined = new SimpleDateFormat(format.format());
					}
					if(StringUtils.isNotBlank(beginValue)) {
						if(userDefined != null) {
							dc.add(Restrictions.ge(aliasName , new Timestamp(time.parse(beginValue).getTime())));
						} else if(beginValue.matches(CommonConsts.LONGDATEREGEX) || beginValue.matches(CommonConsts.DATETIMEREGEX)) {
							dc.add(Restrictions.ge(aliasName , new Timestamp(time.parse(beginValue).getTime())));
						} else if(beginValue.matches(CommonConsts.SHORTDATEREGEX)) {
							dc.add(Restrictions.ge(aliasName , new Timestamp(time.parse(beginValue + " 00:00:00").getTime())));
						}
						parameterMap.remove(name + BEGIN);// 移除范围值,范围值暂不支持递归时继续作为条件
					}
					if(StringUtils.isNotBlank(endValue)) {
						if(userDefined != null) {
							dc.add(Restrictions.ge(aliasName , new Timestamp(time.parse(endValue).getTime())));
						} else if(endValue.matches(CommonConsts.LONGDATEREGEX) || endValue.matches(CommonConsts.DATETIMEREGEX)) {
							dc.add(Restrictions.le(aliasName , new Timestamp(time.parse(endValue).getTime())));
						} else if(endValue.matches(CommonConsts.SHORTDATEREGEX)) {
							// 对于"yyyy-MM-dd"格式日期，因时间默认为0，故此添加" 23:23:59"并使用time解析，以方便查询日期时间数据
							dc.add(Restrictions.le(aliasName , new Timestamp(time.parse(endValue + " 23:23:59").getTime())));
						}
						parameterMap.remove(name + END);// 移除范围值,范围值暂不支持递归时继续作为条件
					}
					if(isNotEmpty(value)) {
						dc.add(Restrictions.eq(aliasName , value));
					}
				} else if(!isJavaClass(origDescriptors[i].getPropertyType())) {
					Object param = PropertyUtils.getSimpleProperty(searchObj , name);
					if(isNotEmpty(param) && itIsNotAllEmpty(param)) {
						// 如果是实体类,创建别名,继续创建查询条件
						dc.createAlias(aliasName , name);
						installHqlCriteriaJoinAlias(dc , param , parameterMap , name);
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * 判定是否为不需要添加条件的的属性
	 * 
	 * @param 需要判定的属性
	 * @return
	 */
	private static boolean judgedIsUselessField(String attrName) {
		return "class".equals(attrName) || "ids".equals(attrName) || "page".equals(attrName) || "rows".equals(attrName) || "sort".equals(attrName) || "order".equals(attrName);
	}
	
	/**
	 * 判断这个类是不是所有属性都为空
	 * 
	 * @param param
	 * @return
	 */
	private static boolean itIsNotAllEmpty(Object param) {
		boolean isNotEmpty = false;
		try {
			PropertyDescriptor origDescriptors [] = PropertyUtils.getPropertyDescriptors(param);
			String name;
			for(int i = 0 ; i < origDescriptors.length ; i++) {
				name = origDescriptors[i].getName();
				if("class".equals(name) || !PropertyUtils.isReadable(param , name)) {
					continue;
				}
				if(Map.class.isAssignableFrom(origDescriptors[i].getPropertyType())) {
					Map<? , ?> map = (Map<? , ?>)PropertyUtils.getSimpleProperty(param , name);
					if(map != null && map.size() > 0) {
						isNotEmpty = true;
						break;
					}
				} else if(Collection.class.isAssignableFrom(origDescriptors[i].getPropertyType())) {
					Collection<?> c = (Collection<?>)PropertyUtils.getSimpleProperty(param , name);
					if(c != null && c.size() > 0) {
						isNotEmpty = true;
						break;
					}
				} else if(isNotEmpty(PropertyUtils.getSimpleProperty(param , name))) {
					isNotEmpty = true;
					break;
				}
			}
		} catch (Exception e) {
			
		}
		return isNotEmpty;
	}
	
	/**
	 * 判断对象是否为空
	 * 
	 * @param str
	 * @return
	 */
	public static boolean isNotEmpty(Object str) {
		boolean flag = true;
		if(str != null && !str.equals("")) {
			if(str.toString().trim().length() > 0) {
				flag = true;
			}
		} else {
			flag = false;
		}
		return flag;
	}
	
	/**
	 * 判断这个类是不是java自带的类
	 * 
	 * @param clazz
	 * @return
	 */
	private static boolean isJavaClass(Class<?> clazz) {
		boolean isBaseClass = false;
		if(clazz.isArray()) {
			isBaseClass = false;
		} else if(clazz.isPrimitive() || clazz.getPackage() == null || clazz.getPackage().getName().equals("java.lang") || clazz.getPackage().getName().equals("java.math") || clazz.getPackage().getName().equals("java.util")) {
			isBaseClass = true;
		}
		return isBaseClass;
	}
}
